
package com.borqs.music.http;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

public abstract class HttpResponse {

    private static ThreadLocal<DocumentBuilder> builders =
            new ThreadLocal<DocumentBuilder>() {
                @Override
                protected DocumentBuilder initialValue() {
                    try {
                        return DocumentBuilderFactory.newInstance()
                                .newDocumentBuilder();
                    } catch (ParserConfigurationException ex) {
                        throw new ExceptionInInitializerError(ex);
                    }
                }
            };

    protected int statusCode;
    private Document responseAsDocument = null;
    protected String responseAsString = null;
    protected InputStream is;
    private boolean streamConsumed = false;

    public final int getStatusCode() {
        return statusCode;
    }

    public abstract String getResponseHeader(String name);

    /**
     * Returns the response stream.<br>
     * This method cannot be called after calling asString() or asDcoument()<br>
     * It is suggested to call disconnect() after consuming the stream.
     * Disconnects the internal HttpURLConnection silently.
     * 
     * @return response body stream
     * @throws Exception
     * @see #disconnect()
     */
    public final InputStream asStream() {
        if (streamConsumed) {
            throw new IllegalStateException("Stream has already been consumed.");
        }

        streamConsumed = true;
        return is;
    }

    /**
     * Returns the response body as string.<br>
     * Disconnects the internal HttpURLConnection silently.
     * 
     * @return response body
     * @throws Exception
     */
    public final String asString() throws Exception {
        if (null == responseAsString) {
            InputStream stream = null;
            try {
                stream = asStream();
                if (null == stream) {
                    return null;
                }
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                byte[] buffer = new byte[1024];
                int len = -1;
                while ((len = stream.read(buffer)) > -1) {
                    baos.write(buffer, 0, len);
                }
                this.responseAsString = baos.toString();
                stream.close();
                streamConsumed = true;
            } catch (NullPointerException npe) {
                throw npe;
            } catch (IOException ioe) {
                throw ioe;
            } finally {
                if (null != stream) {
                    try {
                        stream.close();
                    } catch (IOException ignore) {
                    }
                }
                disconnectForcibly();
            }
        }
        return responseAsString;
    }

    /**
     * Returns the response body as org.w3c.dom.Document.<br>
     * Disconnects the internal HttpURLConnection silently.
     * 
     * @return response body as org.w3c.dom.Document
     * @throws Exception
     */
    public final Document asDocument() throws Exception {
        if (null == responseAsDocument) {
            try {
                // it should be faster to read the inputstream directly.
                // but makes it difficult to troubleshoot
                this.responseAsDocument = builders.get().parse(
                        new ByteArrayInputStream(asString().getBytes("UTF-8")));
            } catch (SAXException saxe) {
                throw new Exception("The response body was not well-formed:\n" + responseAsString,
                        saxe);
            } catch (IOException ioe) {
                throw new Exception("There's something with the connection.", ioe);
            } finally {
                disconnectForcibly();
            }
        }
        return responseAsDocument;
    }

    /**
     * Returns the response body as JSONObject.<br>
     * Disconnects the internal HttpURLConnection silently.
     * 
     * @return response body as JSONObject
     * @throws Exception
     */
    public final JSONObject asJSONObject() throws Exception {
        JSONObject json = null;
        try {
            json = new JSONObject(asString());
        } catch (JSONException jsone) {
            throw new Exception(jsone.getMessage(), jsone);
        } finally {
            disconnectForcibly();
        }
        return json;
    }

    /**
     * Returns the response body as JSONArray.<br>
     * Disconnects the internal HttpURLConnection silently.
     * 
     * @return response body as JSONArray
     * @throws Exception
     */
    public final JSONArray asJSONArray() throws Exception {
        JSONArray json = null;
        try {
            json = new JSONArray(asString());
        } catch (JSONException jsone) {
            throw new Exception(jsone.getMessage(), jsone);
        } finally {
            disconnectForcibly();
        }
        return json;
    }

    private void disconnectForcibly() {
        try {
            disconnect();
        } catch (Exception ignore) {
        }
    }

    public abstract void disconnect() throws IOException;

    @Override
    public String toString() {
        return "HttpResponse{" +
                "statusCode=" + statusCode +
                ", responseAsDocument=" + responseAsDocument +
                ", responseAsString='" + responseAsString + '\'' +
                ", is=" + is +
                ", streamConsumed=" + streamConsumed +
                '}';
    }
}
